"""Provides abstractions for Terminal IO"""

try:
    import pygame
    pygame.display.init() # will fail in non-graphical environment
    PYGAME_ENABLED = True
except:
    PYGAME_ENABLED = False

import os
import sys
from pyre.core import types

_BLANK = types.ColoredChar(' ', types.Color.BLACK)


class Terminal(object):
    """Base class for Terminal abstractions"""

    def __init__(self, cols, rows):
        self.cols, self.rows = cols, rows
        self._buffer = [[_BLANK for _ in range(cols)] for _ in range(rows)]
        self._saved = [[_BLANK for _ in range(cols)] for _ in range(rows)]
        self._widgits = set()

    def close(self):
        """
        Terminal.close(): None
        """

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

    def __del__(self):
        self.close()

    def buffer_char(self, pos, char):
        """
        Terminal.buffer_char(Coordinate, ColoredChar): None
        Places a ColoredChar in the buffer at the given position
        """

        self._buffer[pos[1]][pos[0]] = char

    def char_at(self, pos):
        """
        Terminal.char_at(Coordinate): ColoredChar
        Gets the ColoredChar in the buffer at the given position
        """

        return self._buffer[pos[1]][pos[0]]

    def save_buffer(self):
        """
        Terminal.save_buffer(): None
        Saves the state of the buffer for later recall
        """

        for y in range(self.rows):
            for x in range(self.cols):
                self._saved[y][x] = self._buffer[y][x]

    def clear_buffer(self):
        """
        Terminal.clear_buffer(): None
        Clears the entire buffer
        """

        for y in range(self.rows):
            for x in range(self.cols):
                self._buffer[y][x] = _BLANK

    def recall_buffer(self):
        """
        Terminal.recall_buffer(): None
        Reverts the buffer to the last saved state
        """

        for y in range(self.rows):
            for x in range(self.cols):
                self._buffer[y][x] = self._saved[y][x]

    def refresh_screen(self):
        """
        Terminal.refresh_screen(): None
        Updates the screen to reflect the current state of the buffer
        """

    def get_key(self):
        """
        Terminal.get_key(): chr
        Returns the next key press, blocking as needed
        """

    def add_widgit(self, widgit, pos, dim):
        """
        Terminal.add_widgit(Widgit, Coordinate, Coordinate): None
        Adds a Widgit, placing it at the given position with the given size
        """

        self._widgits.add(widgit)
        widgit.place_widgit(pos, dim)

    def remove_widgit(self, widgit):
        """
        Terminal.remove_widgit(Widgit): None
        Removes a Widgit from the Terminal
        """

        self._widgits.remove(widgit)

    def clear_widgits(self):
        """
        Terminal.add_widgit(): None
        Removes each Widgit associated with the Terminal
        """

        self._widgits.clear()

    def update_widgits(self):
        """
        Terminal.update_widgits(): None
        Updates each Widgit so the buffer reflects the current state of the
        Widgit data sources
        """

        for widgit in self._widgits:
            widgit.update(self)


class AnsiTerminal(Terminal):
    """Uses ANSI escape codes and stty to perform terminal IO"""

    def __init__(self, cols=80, rows=24, title=None):
        Terminal.__init__(self, cols, rows)
        self._settings = os.popen('stty -g').read()
        os.system('stty -echo -icanon')
        os.system('tput civis')
        os.system('clear')
        if title:
            sys.stdout.write('\033]2;{}\007'.format(title))

    def close(self):
        if self._settings is not None:
            os.system('stty {}'.format(self._settings))
            os.system('tput cnorm')
            os.system('clear')
            self._settings = None

    def refresh_screen(self):
        # this is why we index buffer with y,x instead of x,y
        output = [''.join(char.ansi for char in row) for row in self._buffer]
        sys.stdout.write('\033[H') # move cursor to top left corner
        sys.stdout.write('\n'.join(output))

    def get_key(self):
        return sys.stdin.read(1)


class PygameTerminal(Terminal):
    """Uses Pygame to emulate a terminal"""

    def __init__(self, cols=80, rows=24, title=None,
                       fontname='freemono', fontsize=15):
        Terminal.__init__(self, cols, rows)
        pygame.font.init()
        pygame.display.init()

        if title:
            pygame.display.set_caption(title)

        try:
            self.font = pygame.font.Font(fontname, fontsize)
        except IOError:
            self.font = pygame.font.SysFont(fontname, fontsize)
        self.tile_size = max(self.font.size('@'))

        screen_size = self.tile_size * cols, self.tile_size * rows
        self.display = pygame.display.set_mode(screen_size)

        self.render_cache = {}
        self.sprite_cache = {}

    def close(self):
        pygame.quit()
        self.display = None
        self.font = None

    def refresh_screen(self):
        self.display.fill((0, 0, 0, 255))
        for y in range(self.rows):
            for x in range(self.cols):
                pos = (x * self.tile_size, y * self.tile_size)
                self.display.blit(self._render_char(self._buffer[y][x]), pos)
        pygame.display.flip()

    def _render_char(self, char):
        try:
            if char.sprite_id in self.sprite_cache:
                return self.sprite_cache[char.sprite_id]
            else:
                return self.render_cache[char]
        except KeyError:
            render = self.font.render(char.char, True, char.rgba)
            self.render_cache[char] = render
            return render

    def get_key(self):
        event = pygame.event.wait()
        while event.type != pygame.KEYDOWN or len(event.unicode) != 1:
            if event.type == pygame.QUIT:
                raise SystemExit()
            event = pygame.event.wait()
        return event.unicode
